package datastruct.sortedset;

import database.sortedset.JSortedSet;
import datastruct.dict.inter.IDictConsumer;

import java.util.*;
import java.util.concurrent.ConcurrentSkipListMap;


public class JoSortedSet {
    /*
    看起来每条 redis 命令执行时，都会对 key 进行加锁，所以这里用非线程安全的 hashmap 应该也可以
     */
    private ConcurrentSkipListMap<String, Element> mSortedSet;

    public JoSortedSet() {
        mSortedSet = new ConcurrentSkipListMap<>();
    }

    public static JoSortedSet makeSortedSet() {
        return new JoSortedSet();
    }

    public void removeAll() {
        if (mSortedSet == null) {
            return;
        }
        mSortedSet.clear();
    }

    public boolean add(double score, String member) {
        return add(score, member, JSortedSet.UpsertPolicy, JSortedSet.SORTED_SET_UPDATE_ALL, false, false);
    }

    public boolean add(double score, String member, int policy, int scoreUpdatePolicy, boolean isCH, boolean isINCR) {
        Element oldElement = mSortedSet.get(member);
        if (oldElement != null) {

            if (policy == JSortedSet.InsertPolicy || cannotUpdate(scoreUpdatePolicy, score, oldElement.score)) {
                return false;
            }

            if (isINCR) {
                score += oldElement.score;
            }

            // 先删除元素
            mSortedSet.remove(member);
            // 再添加
            mSortedSet.put(member, new Element(score, member));

            // 在没有指定 isCH 选项的情况下，并没有新增，只是更新，所以返回 false
            if (!isCH) {
                return false;
            }
            return true;
        }

        if (policy == JSortedSet.UpdatePolicy) {
            return false;
        }

        mSortedSet.put(member, new Element(score, member));
        return true;
    }

    public boolean contains(String member) {
        return mSortedSet.containsKey(member);
    }

    public Element get(String member) {
        return mSortedSet.get(member);
    }

    public Element[] range(int start, int stop, boolean desc) {
        int i = 0;
        Element[] ret = new Element[stop-start];
        int j = 0;
        Comparator<Map.Entry<String, Element>> comparator = new Comparator<Map.Entry<String, Element>>() {
            @Override
            public int compare(Map.Entry<String, Element> o1, Map.Entry<String, Element> o2) {
                double diff = o1.getValue().score - o2.getValue().score;
                if (desc) {
                    return diff == 0 ? o2.getKey().compareTo(o1.getKey()) : Double.compare(o2.getValue().score, o1.getValue().score);
                }

                return diff == 0 ? o1.getKey().compareTo(o2.getKey()) : Double.compare(o1.getValue().score, o2.getValue().score);
            }
        };

        Iterator<Map.Entry<String, Element>> iter = mSortedSet.entrySet().stream().sorted(comparator).iterator();

        while (iter.hasNext()) {
            if (i >= start && i < stop) {
                ret[j] = iter.next().getValue();
                j++;
            } else {
                iter.next();
            }
            i++;
        }

        return ret;
    }

    public int Len() {
        return mSortedSet.size();
    }

    public boolean remove(String member) {
        return mSortedSet.remove(member) != null;
    }

    public boolean cannotUpdate(int scoreUpdatePolicy, double newScore, double oldScore) {
        return (scoreUpdatePolicy == JSortedSet.SORTED_SET_UPDATE_GREATER && newScore <= oldScore) ||
                (scoreUpdatePolicy == JSortedSet.SORTED_SET_UPDATE_LESS_THAN && newScore >= oldScore);
    }

    // 按照 score 进行 升序/降序 遍历
    public void forEach(boolean desc, ISortedSetConsumer consumer) {
        Comparator<Map.Entry<String, Element>> comparator = (o1, o2) -> {
            double diff = o1.getValue().score - o2.getValue().score;
            if (desc) {
                return diff == 0 ? o2.getKey().compareTo(o1.getKey()) : Double.compare(o2.getValue().score, o1.getValue().score);
            }

            return diff == 0 ? o1.getKey().compareTo(o2.getKey()) : Double.compare(o1.getValue().score, o2.getValue().score);
        };

        Iterator<Map.Entry<String, Element>> iter = mSortedSet.entrySet().stream().sorted(comparator).iterator();

        while (iter.hasNext()) {
            Map.Entry<String, Element> entry = iter.next();
            if (!consumer.consumer(entry.getKey(), entry.getValue())) {
                break;
            }
        }
    }

    public String[] diff(JoSortedSet[] sets) {
        HashMap<String, Element> anotherSet = new HashMap<>();
        for (JoSortedSet set : sets) {
            if (set == null) {
                continue;
            }
            set.forEach(false, new ISortedSetConsumer() {
                @Override
                public boolean consumer(String key, Element val) {
                    anotherSet.put(key, val);
                    return true;
                }
            });
        }

        HashMap<String, Element> result = new HashMap<>();

        for (Map.Entry<String, Element> entry : mSortedSet.entrySet()) {
            if (!anotherSet.containsKey(entry.getKey())) {
                result.put(entry.getKey(), entry.getValue());
            }
        }

        Comparator<Map.Entry<String, Element>> comparator = (o1, o2) -> {
            double diff = o1.getValue().score - o2.getValue().score;
            return diff == 0 ? o1.getKey().compareTo(o2.getKey()) : Double.compare(o1.getValue().score, o2.getValue().score);
        };

        Iterator<Map.Entry<String, Element>> iter = result.entrySet().stream().sorted(comparator).iterator();

        String[] ret = new String[result.size()*2];
        int i = 0;
        while (iter.hasNext()) {
            Map.Entry<String, Element> entry = iter.next();
            ret[i*2] = entry.getKey();
            ret[i*2+1] = Double.toString(entry.getValue().score);
            i++;
        }

        return ret;
    }

    public static JoSortedSet union(JoSortedSet[] sets, double[] weights, int aggregate) {
        int i = 0;
        JoSortedSet ret = new JoSortedSet();
        final int[] index = {0};
        for (JoSortedSet set : sets) {
            if (set == null) {
                continue;
            }

            set.forEach(false, new ISortedSetConsumer() {
                @Override
                public boolean consumer(String key, Element val) {
                    Element item = ret.get(key);
                    double newScore = val.score * weights[index[0]];
                    if (item != null) {
                        switch (aggregate) {
                            case JSortedSet.AGGREGATE_SUM:
                                item.score += newScore;
                                break;
                            case JSortedSet.AGGREGATE_MAX:
                                item.score = Math.max(item.score, newScore);
                                break;
                            case JSortedSet.AGGREGATE_MIN:
                                item.score = Math.min(item.score, newScore);
                                break;
                        }
                        ret.add(item.score, key);
                    } else {
                        ret.add(newScore, key);
                    }

                    return true;
                }
            });
            index[0]++;
        }
        return ret;
    }

    public JoSortedSet applyWeight(double weight) {
        JoSortedSet ret = new JoSortedSet();
        for (Map.Entry<String, Element> entry : mSortedSet.entrySet()) {
            Element val = entry.getValue();
            ret.add(val.score * weight, entry.getKey());
        }
        return ret;
    }

    public JoSortedSet intersection(JoSortedSet otherSet, int aggregate) {
        JoSortedSet ret = new JoSortedSet();
        forEach(false, new ISortedSetConsumer() {
            @Override
            public boolean consumer(String key, Element val) {
                Element otherVal = otherSet.get(key);
                double newScore = val.score;
                if (otherVal != null) {
                    switch (aggregate) {
                        case JSortedSet.AGGREGATE_SUM:
                            newScore += otherVal.score;
                            break;
                        case JSortedSet.AGGREGATE_MAX:
                            newScore = Math.max(newScore, otherVal.score);
                            break;
                        case JSortedSet.AGGREGATE_MIN:
                            newScore = Math.min(newScore, otherVal.score);
                            break;
                    }
                    ret.add(newScore, key);
                }
                return true;
            }
        });
        return ret;
    }

    public static JoSortedSet intersection(JoSortedSet[] sets, double[] weights, int aggregate) {
        if (sets.length == 0) {
            return null;
        }
        int i = 0;
        // 先给每个 set 乘以权重
        JoSortedSet[] setsWithWeights = new JoSortedSet[sets.length];
        for (JoSortedSet set : sets) {
            if (set == null) {
                i++;
                continue;
            }
            setsWithWeights[i] = set.applyWeight(weights[i]);
            i++;
        }

        if (sets.length == 1) {
            return setsWithWeights[0];
        }

        i = 1;
        // 两两求交集
        JoSortedSet ret = setsWithWeights[0].intersection(setsWithWeights[i], aggregate);

        for (i=2;i<sets.length;i++) {
            ret = ret.intersection(setsWithWeights[i], aggregate);
        }
        return ret;
    }

    public static void main(String[] args) {
        JoSortedSet[] sets = new JoSortedSet[2];
        sets[0] = new JoSortedSet();
        sets[0].add(1, "one");
        sets[0].add(2, "two");
        sets[0].add(3, "three");

        sets[1] = new JoSortedSet();
        sets[1].add(1, "one");
        sets[1].add(2, "two");


        String[] ret = sets[0].diff(new JoSortedSet[]{sets[1]});
        for (int i=0;i<ret.length-1;i+=2) {
            System.out.printf("member: %s, score: %s\n", ret[i], ret[i+1]);
        }

    }

}
